/*!
 * jQuery Mobile Touch Events @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Touch
//>>group: Events
//>>description: Touch events including: touchstart, touchmove, touchend, tap, taphold, swipe, swipeleft, swiperight
(function ($) {
	$.mobile = {};
}(jQuery));
(function ($, undefined) {
	var support = {
		touch: "ontouchend" in document
	};

	$.mobile.support = $.mobile.support || {};
	$.extend($.support, support);
	$.extend($.mobile.support, support);
}(jQuery));
(function (factory) {
	if (typeof define === "function" && define.amd) {

		// AMD. Register as an anonymous module.
		define([
			"jquery",
			"../vmouse",
			"../support/touch"
		], factory);
	} else {

		// Browser globals
		factory(jQuery);
	}
})(function ($) {
	var $document = $(document),
		supportTouch = $.mobile.support.touch,
		touchStartEvent = supportTouch ? "touchstart" : "mousedown",
		touchStopEvent = supportTouch ? "touchend" : "mouseup",
		touchMoveEvent = supportTouch ? "touchmove" : "mousemove";

	// setup new event shortcuts
	$.each(("touchstart touchmove touchend " +
		"tap taphold " +
		"swipe swipeleft swiperight").split(" "), function (i, name) {

		$.fn[name] = function (fn) {
			return fn ? this.bind(name, fn) : this.trigger(name);
		};

		// jQuery < 1.8
		if ($.attrFn) {
			$.attrFn[name] = true;
		}
	});

	function triggerCustomEvent(obj, eventType, event, bubble) {
		var originalType = event.type;
		event.type = eventType;
		if (bubble) {
			$.event.trigger(event, undefined, obj);
		} else {
			$.event.dispatch.call(obj, event);
		}
		event.type = originalType;
	}

	// also handles taphold
	$.event.special.tap = {
		tapholdThreshold: 750,
		emitTapOnTaphold: true,
		setup: function () {
			var thisObject = this,
				$this = $(thisObject),
				isTaphold = false;

			$this.bind("vmousedown", function (event) {
				isTaphold = false;
				if (event.which && event.which !== 1) {
					return true;
				}

				var origTarget = event.target,
					timer, clickHandler;

				function clearTapTimer() {
					if (timer) {
						$this.bind("vclick", clickHandler);
						clearTimeout(timer);
					}
				}

				function clearTapHandlers() {
					clearTapTimer();

					$this.unbind("vclick", clickHandler)
						.unbind("vmouseup", clearTapTimer);
					$document.unbind("vmousecancel", clearTapHandlers);
				}

				clickHandler = function (event) {
					clearTapHandlers();

					// ONLY trigger a 'tap' event if the start target is
					// the same as the stop target.
					if (!isTaphold && origTarget === event.target) {
						triggerCustomEvent(thisObject, "tap", event);
					} else if (isTaphold) {
						event.preventDefault();
					}
				};

				$this.bind("vmouseup", clearTapTimer);

				$document.bind("vmousecancel", clearTapHandlers);

				timer = setTimeout(function () {
					if (!$.event.special.tap.emitTapOnTaphold) {
						isTaphold = true;
					}
					timer = 0;
					triggerCustomEvent(thisObject, "taphold", $.Event("taphold", {
						target: origTarget
					}));
				}, $.event.special.tap.tapholdThreshold);
			});
		},
		teardown: function () {
			$(this).unbind("vmousedown").unbind("vclick").unbind("vmouseup");
			$document.unbind("vmousecancel");
		}
	};

	// Also handles swipeleft, swiperight
	$.event.special.swipe = {

		// More than this horizontal displacement, and we will suppress scrolling.
		scrollSupressionThreshold: 30,

		// More time than this, and it isn't a swipe.
		durationThreshold: 1000,

		// Swipe horizontal displacement must be more than this.
		horizontalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30,

		// Swipe vertical displacement must be less than this.
		verticalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30,

		getLocation: function (event) {
			var winPageX = window.pageXOffset,
				winPageY = window.pageYOffset,
				x = event.clientX,
				y = event.clientY;

			if (event.pageY === 0 && Math.floor(y) > Math.floor(event.pageY) ||
				event.pageX === 0 && Math.floor(x) > Math.floor(event.pageX)) {

				// iOS4 clientX/clientY have the value that should have been
				// in pageX/pageY. While pageX/page/ have the value 0
				x = x - winPageX;
				y = y - winPageY;
			} else if (y < (event.pageY - winPageY) || x < (event.pageX - winPageX)) {

				// Some Android browsers have totally bogus values for clientX/Y
				// when scrolling/zooming a page. Detectable since clientX/clientY
				// should never be smaller than pageX/pageY minus page scroll
				x = event.pageX - winPageX;
				y = event.pageY - winPageY;
			}

			return {
				x: x,
				y: y
			};
		},

		start: function (event) {
			var data = event.originalEvent.touches ?
				event.originalEvent.touches[0] : event,
				location = $.event.special.swipe.getLocation(data);
			return {
				time: (new Date()).getTime(),
				coords: [location.x, location.y],
				origin: $(event.target)
			};
		},

		stop: function (event) {
			var data = event.originalEvent.touches ?
				event.originalEvent.touches[0] : event,
				location = $.event.special.swipe.getLocation(data);
			return {
				time: (new Date()).getTime(),
				coords: [location.x, location.y]
			};
		},

		handleSwipe: function (start, stop, thisObject, origTarget) {
			if (stop.time - start.time < $.event.special.swipe.durationThreshold &&
				Math.abs(start.coords[0] - stop.coords[0]) > $.event.special.swipe.horizontalDistanceThreshold &&
				Math.abs(start.coords[1] - stop.coords[1]) < $.event.special.swipe.verticalDistanceThreshold) {
				var direction = start.coords[0] > stop.coords[0] ? "swipeleft" : "swiperight";

				triggerCustomEvent(thisObject, "swipe", $.Event("swipe", {
					target: origTarget,
					swipestart: start,
					swipestop: stop
				}), true);
				triggerCustomEvent(thisObject, direction, $.Event(direction, {
					target: origTarget,
					swipestart: start,
					swipestop: stop
				}), true);
				return true;
			}
			return false;

		},

		// This serves as a flag to ensure that at most one swipe event event is
		// in work at any given time
		eventInProgress: false,

		setup: function () {
			var events,
				thisObject = this,
				$this = $(thisObject),
				context = {};

			// Retrieve the events data for this element and add the swipe context
			events = $.data(this, "mobile-events");
			if (!events) {
				events = {
					length: 0
				};
				$.data(this, "mobile-events", events);
			}
			events.length++;
			events.swipe = context;

			context.start = function (event) {

				// Bail if we're already working on a swipe event
				if ($.event.special.swipe.eventInProgress) {
					return;
				}
				$.event.special.swipe.eventInProgress = true;

				var stop,
					start = $.event.special.swipe.start(event),
					origTarget = event.target,
					emitted = false;

				context.move = function (event) {
					if (!start || event.isDefaultPrevented()) {
						return;
					}

					stop = $.event.special.swipe.stop(event);
					if (!emitted) {
						emitted = $.event.special.swipe.handleSwipe(start, stop, thisObject, origTarget);
						if (emitted) {

							// Reset the context to make way for the next swipe event
							$.event.special.swipe.eventInProgress = false;
						}
					}
					// prevent scrolling
					if (Math.abs(start.coords[0] - stop.coords[0]) > $.event.special.swipe.scrollSupressionThreshold) {
						event.preventDefault();
					}
				};

				context.stop = function () {
					emitted = true;

					// Reset the context to make way for the next swipe event
					$.event.special.swipe.eventInProgress = false;
					$document.off(touchMoveEvent, context.move);
					context.move = null;
				};

				$document.on(touchMoveEvent, context.move)
					.one(touchStopEvent, context.stop);
			};
			$this.on(touchStartEvent, context.start);
		},

		teardown: function () {
			var events, context;

			events = $.data(this, "mobile-events");
			if (events) {
				context = events.swipe;
				delete events.swipe;
				events.length--;
				if (events.length === 0) {
					$.removeData(this, "mobile-events");
				}
			}

			if (context) {
				if (context.start) {
					$(this).off(touchStartEvent, context.start);
				}
				if (context.move) {
					$document.off(touchMoveEvent, context.move);
				}
				if (context.stop) {
					$document.off(touchStopEvent, context.stop);
				}
			}
		}
	};
	$.each({
		taphold: "tap",
		swipeleft: "swipe.left",
		swiperight: "swipe.right"
	}, function (event, sourceEvent) {

		$.event.special[event] = {
			setup: function () {
				$(this).bind(sourceEvent, $.noop);
			},
			teardown: function () {
				$(this).unbind(sourceEvent);
			}
		};
	});

	return $.event.special;
});

/*!
 * jQuery Mobile Scroll Events @VERSION
 * http://jquerymobile.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Scroll
//>>group: Events
//>>description: Scroll events including: scrollstart, scrollstop

(function (factory) {
	if (typeof define === "function" && define.amd) {

		// AMD. Register as an anonymous module.
		define(["jquery"], factory);
	} else {

		// Browser globals
		factory(jQuery);
	}
})(function ($) {

	var scrollEvent = "touchmove scroll";

	// setup new event shortcuts
	$.each(["scrollstart", "scrollstop"], function (i, name) {

		$.fn[name] = function (fn) {
			return fn ? this.bind(name, fn) : this.trigger(name);
		};

		// jQuery < 1.8
		if ($.attrFn) {
			$.attrFn[name] = true;
		}
	});

	// also handles scrollstop
	$.event.special.scrollstart = {

		enabled: true,
		setup: function () {

			var thisObject = this,
				$this = $(thisObject),
				scrolling,
				timer;

			function trigger(event, state) {
				var originalEventType = event.type;

				scrolling = state;

				event.type = scrolling ? "scrollstart" : "scrollstop";
				$.event.dispatch.call(thisObject, event);
				event.type = originalEventType;
			}

			var scrollStartHandler = $.event.special.scrollstart.handler = function (event) {

				if (!$.event.special.scrollstart.enabled) {
					return;
				}

				if (!scrolling) {
					trigger(event, true);
				}

				clearTimeout(timer);
				timer = setTimeout(function () {
					trigger(event, false);
				}, 50);
			};

			// iPhone triggers scroll after a small delay; use touchmove instead
			$this.on(scrollEvent, scrollStartHandler);
		},
		teardown: function () {
			$(this).off(scrollEvent, $.event.special.scrollstart.handler);
		}
	};

	$.each({
		scrollstop: "scrollstart"
	}, function (event, sourceEvent) {

		$.event.special[event] = {
			setup: function () {
				$(this).bind(sourceEvent, $.noop);
			},
			teardown: function () {
				$(this).unbind(sourceEvent);
			}
		};
	});

	return $.event.special;
});